import tkinter as tk  
from tkinter import ttk 
import serial.tools.list_ports
from datetime import datetime
from tkinter import filedialog 
import auto_run
import threading
import time
from tkinter import messagebox 
from tkinter import Listbox
import uiautomator2 as u2
import subprocess
import rtc_time
import sys

# import ctypes  # 用于调用Windows系统API

def list_serial_ports():  
    """列出系统中可用的串行端口"""  
    my_com = {}
    ports = list(serial.tools.list_ports.comports())  
    for port, desc, hwid in ports:  
        print(f"{port} {desc or '无'}") 
        my_com[port] = desc or '无'
        
    return my_com
        
class MyCom:  
    def on_selection_change(self, event):  
        self.com_number = self.combobox.current()  
        print(f"当前选中的值: {self.combobox.get()}")  
        self.com_port = self.keys_list[self.com_number] 
        self.p_data.set_com(self.com_port)
        
    def __init__(self, parent, p_data):  
        self.parent = parent
        self.p_data = p_data
        # 假设的COM口列表  
        self.com_port = ''  
        self.com_number = -1
        self.my_all_com_dict = {}
        
        self.my_all_com_dict = list_serial_ports()
        self.keys_list = list(self.my_all_com_dict.keys())  
        self.values_list = list(self.my_all_com_dict.values())  
        
        for item in self.values_list:  
            if item.find("DIAG") != -1:  
                print(f"Found 'DIAG' in '{item}'")
                try:
                    ser = serial.Serial(self.keys_list[self.com_number + 1], "115200", timeout = 1)
                    ser.close()  
                    self.com_number += 1
                    break
                except Exception:  
                    print("串口被占用:"+self.values_list[self.com_number + 1])
        
            self.com_number +=1
            
        self.com_port = self.keys_list[self.com_number] 
        
        self.selected_port = tk.StringVar() 
        self.selected_port.set(self.values_list[self.com_number] )  # 默认选择第一个COM口  
        
        # 创建Combobox并填充COM口列表  
        self.combobox = ttk.Combobox(self.parent, textvariable=self.selected_port, values=self.values_list, width=18)
        self.combobox.pack(side=tk.LEFT, padx=5)
        self.combobox.bind("<<ComboboxSelected>>", self.on_selection_change)
          
class RunButton:  
    def __init__(self, parent, p_data):  
        self.parent = parent
        self.p_data = p_data
        # 创建按钮，并将其引用保存在实例变量中  
        self.button = tk.Button(self.parent, text="开始", command=self.on_button_click, bg='#00FFFF', width=10, height=1)  
        self.button.pack(side=tk.RIGHT, padx=5)
  
    def on_button_click(self):  
        # 更新按钮的文本  
        if self.p_data.run_status == False:
            self.button.config(text="停止")
        else:
            self.button.config(text="开始")
            
        self.p_data.run_status = not self.p_data.run_status

class Text:
    def __init__(self, parent, log_file_path):  
        self.parent = parent
        self.log_file_path = log_file_path
        self.text_area = tk.Text(self.parent, height=25, width=98)  
        self.text_area.pack(padx=5, pady=5, fill=tk.BOTH, expand=True)
  
    def print_text(self,text):  
        # 获取当前时间  
        now = datetime.now()  
        # 如果你想要以特定的格式打印时间，可以使用strftime方法  
        formatted_now = now.strftime("%m-%d %H:%M:%S")  
        # 将文本写入文件  
        with open(self.log_file_path + ".txt", "a") as file: 
            file.write(formatted_now + ": "+ text+'\n')  
            
        self.text_area.insert(tk.END, formatted_now + ":"+ text + '\n')
        self.text_area.yview(tk.END) 
        
class LoopCountEntry:  
    def validate_entry(self,P):  
        if P.isdigit():  # 检查输入是否全为数字  
            return True  
        else:  
            messagebox.showerror("输入错误", "只能输入数字！")  
            return False  
    
    def on_text_change(self,event):  
        print("Text has changed:", self.entry.get())
        self.p_data.set_loop_count(self.entry.get())
        
    def __init__(self, parent, def_count, p_data): 
        self.parent = parent
        self.def_count = def_count
        self.p_data = p_data

        default_value = tk.StringVar(value="10")  
        vcmd = (parent.register(self.validate_entry), '%P')  # '%P'代表Entry中的内容
        # 使用Entry控件  
        self.entry = tk.Entry(self.parent, validate="key", validatecommand=vcmd, width=6, textvariable=default_value)  
        self.entry.pack(side=tk.LEFT, padx=5)
        # 或者绑定FocusOut事件，当焦点离开时触发  
        self.entry.bind("<KeyRelease>", self.on_text_change)

class FirmwareInput:  
    def is_valid_path(path):
        """检查路径是否有效"""
        if not path:
            return False
        
        # 检查路径是否包含非法字符
        invalid_chars = ['<', '>', ':', '"', '/', '\\', '|', '?', '*']
        if any(char in path for char in invalid_chars):
            return False
        
        # 检查路径是否过长（Windows限制为260个字符）
        if len(path) > 260:
            return False
        
        return True
    
    def on_text_change(self,event):  
        print("Text has changed:", self.entry.get())
        self.p_data.set_firmware_path(self.entry.get())
        
    def __init__(self, parent, p_data): 
        self.parent = parent
        self.p_data = p_data 
        default_value = tk.StringVar(value="")  
        vcmd = (parent.register(self.is_valid_path), '%P')  # '%P'代表Entry中的内容
        # 使用Entry控件  
        self.entry = tk.Entry(self.parent, validate="key", validatecommand=vcmd, width=40, textvariable=default_value)  
        self.entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        # 或者绑定FocusOut事件，当焦点离开时触发  
        self.entry.bind("<KeyRelease>", self.on_text_change)

class RunExeInput:  
    def is_valid_path(path):
        """检查路径是否有效"""
        if not path:
            return False
        
        # 检查路径是否包含非法字符
        invalid_chars = ['<', '>', ':', '"', '/', '\\', '|', '?', '*']
        if any(char in path for char in invalid_chars):
            return False
        
        # 检查路径是否过长（Windows限制为260个字符）
        if len(path) > 260:
            return False
        
        return True
    
    def on_text_change(self,event):  
        print("Text has changed:", self.entry.get())
        self.p_data.set_run_exe_path(self.entry.get())
        
    def __init__(self, parent, p_data): 
        self.parent = parent
        self.p_data = p_data 
        default_value = tk.StringVar(value="")  
        vcmd = (parent.register(self.is_valid_path), '%P')  # '%P'代表Entry中的内容
        # 使用Entry控件  
        self.entry = tk.Entry(self.parent, validate="key", validatecommand=vcmd, width=40, textvariable=default_value)  
        self.entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        # 或者绑定FocusOut事件，当焦点离开时触发  
        self.entry.bind("<KeyRelease>", self.on_text_change)
        
class SelectButton:     
    def select_directory(self):  
        # 弹出文件选择对话框  
        # filetypes参数用于指定文件类型过滤器，'*.txt'表示只显示文本文件  
        # 第一个元组中的''表示默认类型，这里留空表示无默认类型  
        directory = filedialog.askdirectory(initialdir=self.last_directory)  
        # 将选中的文件路径显示在标签上  
        self.select_label.config(text=directory) 
        self.p_data.set_directory(directory)
        
    def __init__(self, parent, last_directory, p_data):  
        self.parent = parent
        self.p_data = p_data
        self.last_directory = last_directory
        # 创建一个标签，用于显示选中的文件路径  
        self.select_label = tk.Label(self.parent, text="未选择目录", width=50, anchor='w')  
        self.select_label.pack(side=tk.LEFT, padx=5)
        
        # 创建一个按钮，点击时调用select_file函数  
        self.button = tk.Button(self.parent, text="选择目录", command=self.select_directory)  
        self.button.pack(side=tk.LEFT, padx=5)

class MyData: 
    def __init__(self, com_port='COM1',baudrate='115200',directory='./',loop_count=10,run_status=False): 
        self.com_port = com_port
        self.baudrate = baudrate
        self.directory = directory
        self.loop_count = loop_count
        self.firmware_path = ''
        self.download_time = ''
        self.run_exe_path = ''
        self.run_status = run_status
        self.phone_device = None
        
    def set_com(self,com_port):
        self.com_port = com_port
        
    def set_baudrate(self,baudrate):
        self.baudrate = baudrate
        
    def set_directory(self,directory):
        self.directory = directory
    
    def set_loop_count(self,loop_count):
        self.loop_count = loop_count
        
    def set_firmware_path(self,firmware_path):
        self.firmware_path = firmware_path
    def set_download_time(self,download_time):
        self.download_time = download_time

    def set_run_exe_path(self,run_exe_path):
        self.run_exe_path = run_exe_path
    def set_run_status(self,run_status):  
        self.run_status = run_status
        
    def set_phone_device(self,phone_device):
        self.phone_device = phone_device
           

def thread_auto_run(stop_event,root,my_data,p_disp_Text,p_ini,ini_file_path,p_run_button,p_listbox):
    while True and not stop_event.is_set():
        time.sleep(2) # 休眠1秒
        if my_data.run_status:
            p_ini.ini_write(ini_file_path+"config.ini",my_data.com_port,my_data.baudrate,my_data.directory)
            # 获取当前选中项的索引列表（如果是多选模式，则可能有多个索引）  
            index = p_listbox.curselection() 
            if(len(index) > 0):
                select_box = p_listbox.get(index)
            else:
                select_box = None
            auto_run.auto_test_main(root,my_data,p_disp_Text,select_box)
        else:
            p_run_button.button.config(text="开始")

def tips(root):
    messagebox.showinfo("提示", "选择脚本目录(该目录下所有脚本顺序执行),COM口,运行次数,点击运行即可")  


def get_connected_devices():  
    """ 使用adb命令获取所有连接的ADB设备列表 """  
    try:  
        # 执行adb devices命令并捕获输出  
        result = subprocess.check_output(['adb', 'devices']).decode('utf-8')  
        # 提取设备序列号（跳过表头和空行）  
        devices = [line.split('\t')[0] for line in result.strip().split('\n')[1:] if line.strip()]  
        return devices  
    except subprocess.CalledProcessError:  
        return []  
  
def update_device_list(device_listbox):  
    """ 更新Tkinter列表框中的设备列表 """  
    devices = get_connected_devices()  
    device_listbox.delete(0, tk.END)  # 清空列表框  
    for device in devices:  
        device_listbox.insert(tk.END, device)  # 插入新设备
        # 默认选中第一行数据  
        device_listbox.selection_set(0)  # 0 是第一行的索引  
        # （可选）如果你想要滚动条也滚动到选中的行，可以调用 see 方法  
        device_listbox.see(0)  
  
def main():
    print(f"Python 版本：{sys.version}")
    # 创建主窗口  
    root = tk.Tk()  
    root.geometry("1000x700+500+200")
    root.minsize(1000, 700)  # 固定最小尺寸，防止缩小
    root.resizable(True, True)  # 启用窗口手动缩放    

    ini_file_path = "./"
    log_file_path = "./"
    
    # 获取当前时间  
    now = datetime.now()  
    # 打印当前时间  
    print("当前时间:", now)  
    # 如果你想要以特定的格式打印时间，可以使用strftime方法  
    formatted_now = now.strftime("%Y-%m-%d %H:%M:%S")  
    log_file_path += formatted_now.replace(":", "_")
    formatted_now_version = now.strftime("%Y_%m_%d")
    root.title("RTOS 自动化测试软件 "+ formatted_now_version +" V1.7")

    # 创建主框架
    main_frame = tk.Frame(root)
    main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
    
    # 解析本地ini
    p_ini = auto_run.INI()
    
    if auto_run.is_file_exist(ini_file_path+"config.ini"):
        p_ini.ini_read(ini_file_path+"config.ini")
    else:
        p_ini.ini_write(ini_file_path+"config.ini",p_ini.com_port,'115200','')
        
    p_data = MyData(p_ini.com_port,'115200',p_ini.directory,10,False)
    
    # 创建控制面板框架
    ctrl_frame = tk.LabelFrame(main_frame, text="控制面板", padx=10, pady=10)
    ctrl_frame.pack(fill=tk.X, pady=(0, 10))
    
    # COM端口选择区域
    com_frame = tk.Frame(ctrl_frame)
    com_frame.pack(fill=tk.X, pady=5)
    
    COM_label = tk.Label(com_frame, text="COM端口:")  
    COM_label.pack(side=tk.LEFT, padx=(0, 5))
    
    # 创建一个串口并自动选中
    p_com = MyCom(com_frame, p_data)
    
    # 循环次数设置区域
    loop_frame = tk.Frame(ctrl_frame)
    loop_frame.pack(fill=tk.X, pady=5)
    
    lool_label = tk.Label(loop_frame, text="循环次数:")  
    lool_label.pack(side=tk.LEFT, padx=(0, 5))
    
    p_loop_label = LoopCountEntry(loop_frame, 1, p_data)
    
    # 目录选择区域
    dir_frame = tk.Frame(ctrl_frame)
    dir_frame.pack(fill=tk.X, pady=5)
    
    dir_label = tk.Label(dir_frame, text="脚本目录:")  
    dir_label.pack(side=tk.LEFT, padx=(0, 5))
    
    # 创建选择目录
    p_select_button = SelectButton(dir_frame, p_ini.directory, p_data)
    p_select_button.select_label.config(text=p_ini.directory)
    
    # 固件路径设置区域
    firmware_frame = tk.Frame(ctrl_frame)
    firmware_frame.pack(fill=tk.X, pady=5)
    
    lool_firmware = tk.Label(firmware_frame, text="固件路径:")  
    lool_firmware.pack(side=tk.LEFT, padx=(0, 5))
    
    p_firmware_label = FirmwareInput(firmware_frame, p_data)
    
    # 运行程序路径设置区域
    run_exe_frame = tk.Frame(ctrl_frame)
    run_exe_frame.pack(fill=tk.X, pady=5)
    
    run_exe_label = tk.Label(run_exe_frame, text="运行程序:")  
    run_exe_label.pack(side=tk.LEFT, padx=(0, 5))
    
    p_run_exe_label = RunExeInput(run_exe_frame, p_data)
    
    # 时间设置和运行按钮区域
    button_frame = tk.Frame(ctrl_frame)
    button_frame.pack(fill=tk.X, pady=5)
    
    ttk.Button(button_frame, text="下载时间", command=lambda: rtc_time.TimePicker(root, p_data, callback=show_time)).pack(side=tk.LEFT, padx=(0, 5))
    time_label = tk.Label(button_frame, text="")
    time_label.pack(side=tk.LEFT, padx=(0, 10))
    
    # 创建一个按钮
    p_run_button = RunButton(button_frame, p_data)
    
    # 创建更新手机列表按钮
    update_button = tk.Button(button_frame, text="更新手机列表", command=lambda: update_device_list(device_listbox))
    update_button.pack(side=tk.RIGHT, padx=(5, 0))
    
    # 创建主要内容框架（包含日志显示和设备列表）
    content_frame = tk.Frame(main_frame)
    content_frame.pack(fill=tk.BOTH, expand=True)
    
    # 创建日志显示区域
    log_frame = tk.LabelFrame(content_frame, text="运行日志", padx=5, pady=5)
    log_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
    
    p_disp_Text = Text(log_frame, log_file_path)
    
    # 创建设备列表区域
    device_frame = tk.LabelFrame(content_frame, text="连接设备", padx=5, pady=5)
    device_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=(10, 0))
    
    # 创建列表框  
    device_listbox = Listbox(device_frame, width=25, height=10, selectmode=tk.SINGLE)  
    device_listbox.pack()
    
    # 初始更新设备列表
    update_device_list(device_listbox)

    # 重新配置串口
    p_data.set_com(p_com.com_port)
    
    def show_time(p_data, hour, minute):
        time_str = f"{hour:02d}:{minute:02d}"
        time_label.config(text=f"#{time_str}")
        print(time_str)
        p_data.set_download_time(time_str)
    
    # # 创建一个Text小部件  
    p_disp_Text.print_text("==============start==============")
    p_disp_Text.print_text("\n1. 选择执行目录,按目录中excel名称顺序循环执行.\n2. excel中\"步骤\"列相同的行,会随机执行其中一行,做到随机点击联系人功能")
    
    # 创建一个停止事件  
    stop_event = threading.Event()
    
    # 创建线程并传递数据  
    thread = threading.Thread(target=thread_auto_run, args=(stop_event,root,p_data,p_disp_Text,p_ini,ini_file_path,p_run_button,device_listbox,))  
    # 启动线程  
    thread.start()  
    # 创建线程并传递数据  
    thread_tips = threading.Thread(target=tips, args=(root,))  
    # 启动线程  
    thread_tips.start()  
    
    # 进入主事件循环 
    root.mainloop()
    
    # 当窗口关闭时，设置停止事件  
    stop_event.set()


# 如果这个脚本是作为主程序运行（而不是被导入），则调用main函数  
if __name__ == "__main__":  
    main()   